package scala.examples.tcpoly.idioms;

trait experiment {
  case class Person(age: Int, var money: Int)

  val ppl = List(Person(10, 0), Person(20, 10), Person(30, 100))
}

trait Traversal {
  def traverse[a, b](l: List[a])(f: a => b)(implicit identity: b, combine : (b, b) => b): b = {
    trait Combinator{val a: b; def &(b: b) = combine(a,b)}; implicit def infixMagic(x: b) = new Combinator{val a=x}  
    l match {
      case Nil => identity
      case x :: xs => identity & f(x) & traverse(xs)(f) 
    }
  }
}

// int monoid
object counting extends experiment with Traversal {
  var count = 0
  for(p <- ppl) {
    count += 1
  }
  
  def myTraverse[a](l: List[a])(f: a => Int): Int = {
    l match {
      case Nil => 0
      case x :: xs => f(x) + myTraverse(xs)(f) 
    }
  }
  
  val count_1 = myTraverse(ppl)(x => 1)
  val count_2 = traverse(ppl)(x => 1)(0, _ + _)
}

// list monoid
object filter extends experiment with Traversal{
  val youngOnes = for(p <- ppl; if p.age <= 25) yield p

  def myTraverse[a](l: List[a])(f: a => List[a]): List[a] = {
    l match {
      case Nil => Nil
      case x :: xs => f(x) ::: myTraverse(xs)(f) 
    }
  }
  
  val youngOnes_1 = myTraverse(ppl)(p => if(p.age <= 25) List(p) else Nil)
  val youngOnes_2 = traverse(ppl)(p => if(p.age <= 25) List(p) else Nil)(Nil, _ ::: _)
}    


trait Traversal3{
  type m[_]
  
  def curry[a, b, c](f: (a, b) => c): a => b => c = (a: a) => (b: b) => f(a, b)
  def lift[a](x: a): m[a] 
  def combine[S, T](f: m[S => T], a: m[S]): m[T] 

  def traverse[a, b](l: List[a])(f: a => m[b]): m[List[b]] = {
    l match {
      case Nil => lift(Nil)
      case x :: xs => combine(combine(lift(curry[b, List[b], List[b]](::)), f(x)), traverse(xs)(f)) 
    }
  }
}

object mapping extends experiment with Traversal3 {
  val ages = for(p <- ppl) yield p.age
  
  
  def myTraverse[a, b](l: List[a])(f: a => b): List[b] = {
    l match {
      case Nil => Nil
      case x :: xs => f(x) :: myTraverse(xs)(f) 
    }
  }
  
  val ages_1 = myTraverse(ppl)(_.age)
  
  type m[x] = x
  def lift[a](x: a): m[a] = x
  def combine[S, T](f: m[S => T], a: m[S]): m[T] = f(a)
  
  val ages_2 = traverse(ppl)(_.age)
}    

object mutate extends experiment with Traversal3 {
  val richOnes = for(p <- ppl) { p.money += 100 }

  trait Env[Key <: AnyRef, Val] extends (Key => Val) {
    def apply(k: Key): Val
    def update(k: Key, v: Val) = new Env[Key, Val] {
      def apply(kk: Key) = if(k eq kk) v else Env.this(kk)
      def toString(shown: List[Key]) = (if(!shown.contains(k)) k+" -> "+v+", " else "")+Env.this.toString(k::shown)
      override def toString = toString(Nil)
    }
    def toString(shown: List[Key]): String
  }
  def empty[a <: AnyRef, b] = new Env[a, b] {
    def apply(k: a): b = error("Empty environment")
    override def toString = ""; def toString(xs: List[a]) = ""
  }
  
  def myTraverse[a <: AnyRef, b](l: List[a])(f: a => Env[a, b] => Env[a, b]): Env[a, b] => Env[a, b] = {
    l match {
      case Nil => (e: Env[a, b]) => e
      case x :: xs => (e: Env[a, b]) => myTraverse(xs)(f)(f(x)(e)) 
    }
  }
  val initialEnv = ppl.foldLeft(empty[Person, Int])((e, p) => e(p) = p.money)
  val richOnes_1 = myTraverse(ppl)(p => (env: Env[Person, Int]) => env(p) += 100)(initialEnv)

  type m[x] = Env[Person, Int] => Env[Person, Int]
  def lift[a](x: a): m[a] = (e: Env[Person, Int]) => e
  def combine[S, T](f: m[S => T], a: m[S]): m[T] = (e: Env[Person, Int]) => f(a(e))
  
  val richOnes_2 = traverse(ppl)(p => (env: Env[Person, Int]) => env(p) += 100)(initialEnv)
}    

object Test extends Application {
  Console.println(mutate.richOnes_1)
  Console.println(mutate.richOnes_2)
}


// int monoid
object accum extends experiment {
  var sum = 0
  for(p <- ppl) {
    sum += p.age
  }
  
  def traverse[a, b](l: List[a])(f: a => b)(implicit identity: b, combine : (b, b) => b): b = {
    trait Combinator{val a: b; def &(b: b) = combine(a,b)}; implicit def infixMagic(x: b) = new Combinator{val a=x}  
    l match {
      case Nil => identity
      case x :: xs => identity & f(x) & traverse(xs)(f) 
    }
  }
  
  val sum_ = traverse(ppl)(_.age)(0, _ + _)
}





/*
  trait Functor[m[_], a] requires (Functor[m,a] with m[a]) {
    // original first argument: this
    def fmap[b](f: a => b): m[b]
  }

  abstract class Applicative[m[x] <: Functor[m, x], a] requires (Applicative[m, a] with m[a]) {
    def pure(x: a): m[a]
    
    // original second argument: this
    def *: [b](fun: m[a =>b]): m[b]
  }

  abstract class Monoid[a] requires (Monoid[a] with a) {
    val identity: a
    
    def op(y: a): a
  }

//  encoding of type Const[t][_] = t
//  Const[t] --> Const[t]#apply

  abstract class ApplicativeMonoid[b <: Monoid[b]] extends Applicative[*/

    /*trait Traversal2 {
      trait HOFunction1[from[_], to[_]] {
        def apply[x](a: from[x]): to[x]
      }
      type Id[x] = x
      trait Const[t] { type apply[_] = t }

      object id extends HOFunction1[Id, Id] {
        def apply[x](a: x): x = a
      }

      def traverse[a, b, m[_]](l: List[a])(f: a => b)(implicit lift: HOFunction1[Id, m], 
                               combine : (m[(b, List[b]) => List[b]], m[b]) => m[List[b]]): m[List[b]] = {
        //trait Combinator{val a: b; def &(b: b) = combine(a,b)}; implicit def infixMagic(x: b) = new Combinator{val a=x}  
        l match {
          case Nil => lift(Nil)
          case x :: xs => combine(combine(lift((x: a) => (xs: List[a]) => x :: xs), f(x)), traverse(xs)(f)) 
        }
      }
    }*/
    
